﻿package fab.geom
{
	import flash.geom.Point;
	import flash.geom.Rectangle;

	public class Circle
	{
		static public var PI2:Number=Math.PI * 2;
		public var rayon:Number=0;
		public var centre:Point;
		public var bounds:Rectangle;
		public var name:String;

		public function Circle( c:Point, r:Number, n:String="cercle" )
		{
			centre = c;
			rayon = r;
			name = n;
			bounds = new Rectangle( c.x - r, c.y - r, 2*r, 2*r );
		}
		public function update( c:Point, r:Number=0 ):void
		{
			centre = c;
			if ( r != 0 ) rayon = r;
			bounds = new Rectangle( c.x - r, c.y - r, 2*r, 2*r );
		}		
		/*
		 * projection d'un point sur le cercle, 
		 * attract = true : seulement si le point est en dehors
		 * attract = false : seulement si le point est dedans
		 * toujours = true : dans tous les cas
		 * */
		public function projection( p:Point, attract:Boolean = true, toujours:Boolean = false ):Point
		{
			var d:Number = rayon / Point.distance( centre, p );
			if ( toujours ) return Point.interpolate( p, centre, d );
			else if ( (attract?1:-1)*( d - 1 ) < 0 ) return Point.interpolate( p, centre, d );
			else return p;
		}
		public function containsPoint( p:Point ):Boolean
		{
			return Point.distance( centre, p ) <= rayon;
		}
		public function intersectsRectangle( rect:Rectangle ):Boolean
		{
			if ( !bounds.intersects( rect ) ) return false;
			else {
				if ( Point.distance( centre, rect.bottomRight ) <= rayon ) return true;
				if ( Point.distance( centre, rect.bottomRight.add( new Point( -rect.width, 0 ) ) ) <= rayon ) return true;
				if ( Point.distance( centre, rect.bottomRight.add( new Point( 0, -rect.height ) ) ) <= rayon ) return true;
				if ( Point.distance( centre, rect.topLeft ) <= rayon ) return true;
			}

			return false;
		}	
		static public function intersects( c1:Circle, c2:Circle ):Boolean
		{
			if ( Point.distance( c1.centre, c2.centre ) <= c1.rayon + c2.rayon ) return true;
			else return false;
		}
		/*
		 * sort les points et control points de l'arc depuis from à to dans le sens trigo inverse (radians)
		 * */
		public function pointAt( angle:Number=0 ):Point
		{
			return centre.add( Point.polar( rayon, angle ) );
		}
		/*
		 * sort les points et control points de l'arc depuis from à to dans le sens trigo inverse (radians)
		 * */
		public function get_arc_dots( from:Number=0, to:Number=Math.PI*2 ):Vector.<Point>
		{
			var _coord:Vector.<Point> = new Vector.<Point>();
			var segAngle:Number;
			var angle:Number;
			
			if ( from < 0 || from > PI2 ) from = ( from + PI2 ) % ( PI2 );
			if ( to < 0 || to > PI2 ) to = ( to + PI2 ) % ( PI2 );
			if ( to < from ) to += PI2;
			var steps:int = Math.ceil( ( to - from ) / ( Math.PI / 4 ) );
			segAngle = Math.abs(( to - from ) ) / steps;
			
			angle = from;
			_coord.push( centre.add( Point.polar( rayon, from ) ) );
			var k:int = 0;
			while( k++ < steps )
			{
				angle += segAngle;
				_coord.push( centre.add( Point.polar( rayon / Math.cos( segAngle / 2 ), angle - ( segAngle / 2 ) ) ) );// point de controle : rayon augmenté
				_coord.push( centre.add( Point.polar( rayon, angle ) ) );
			}
			return _coord;	
		}			
		/*
		 * 
		 * 
		 * 
		 * */
		static public function rad2Deg(r:Number):int
		{
			return r * 180 / Math.PI; 
		}		
		static public function positive_angle( a:Number ):Number
		{
			if ( a < 0 || a > PI2 ) a = ( a + PI2 ) % ( PI2 );
			return a;
		}
		/*
		 * resolution equation degre 2 des equations de cercle
		 * */
		static public function intersection( c1:Circle, c2:Circle ):Segment
		{
			if ( !intersects( c1, c2 ) ) return null;
			var x0:Number = c1.centre.x;
			var y0:Number = c1.centre.y;
			var r0:Number = c1.rayon;
			var x1:Number = c2.centre.x;
			var y1:Number = c2.centre.y;
			var r1:Number = c2.rayon;
			var x:Number;
			var y:Number;
			var a:Number;
			var b:Number;
			var c:Number;
			var det:Number;
			var p:Point;
			if ( y0 == y1 ) 
			{
				x = ( r1 * r1 - r0 * r0 - x1 * x1 + x0 * x0 ) / ( 2 * ( x0 - x1 ) );
				a = 1;
				b = - 2 * y1;
				c = x1 * x1 + x * x - 2 * x1 * x + y1 * y1 - r1 * r1;
				det = Math.sqrt( Math.abs( b * b - 4 * a * c ) );
				y = ( - b + det ) / ( 2 * a );
				p = new Point( x, y ).clone();
				y = ( - b - det ) / ( 2 * a );
				return new Segment( p, new Point( x, y ) );				
			}
			else {
				var N:Number = ( r1 * r1 - r0 * r0 - x1 * x1 + x0 * x0 - y1 * y1 + y0 * y0 ) / ( 2 * (y0 - y1) );
				var ratio:Number = ( x0 - x1 ) / ( y0 - y1 );
				a = Math.pow( ratio, 2 ) + 1;
				b = 2 * y0 * ratio - 2 * N * ratio - 2 * x0;
				c = x0 * x0 + y0 * y0 + N * N - r0 * r0 - 2 * y0 * N;
				det = Math.sqrt( Math.abs( b * b - 4 * a * c ) );
				if ( det < 0 ) return null;

				//trace("determinant intersection cercle : ", det);
				x = ( - b + det ) / ( 2 * a );
				y = N - x * ratio;
				p = new Point( x, y ).clone();
				x = ( - b - det ) / ( 2 * a );
				y = N - x * ratio;
				return new Segment( p, new Point( x, y ) );
			}
		}
		/*
		 * résolution par arccosinus du projeté sur l'axe des centres
		 * rend l'objet information intersection : angles des points d'intersection p/r aux 2 cercles
		 * */
		static public function get_intersect_info( c1:Circle, c2:Circle ):Object
		{
			var d:int = Point.distance( c1.centre, c2.centre );
			if ( d > c1.rayon + c2.rayon ) return null;// pas d'intersection
			if ( d == c1.rayon + c2.rayon ) return {state:"touche"};// cercles collés : 1 point d'intersection
			if ( d + c1.rayon <= c2.rayon ) return {state:"inclusion1"};
			if ( d + c2.rayon <= c1.rayon ) return {state:"inclusion2"};

			var vecteur:Point = c2.centre.subtract( c1.centre );
			var angle:Number = vecteur.x == 0?0:Math.atan2( vecteur.y, vecteur.x );// pente de l'axe des centres, radians

			// calcul des distances du projeté d'intersection aux centres des cercles
			//http://mathworld.wolfram.com/Circle-CircleIntersection.html
			var d_proj_c1:Number = ( d * d + c1.rayon * c1.rayon - c2.rayon * c2.rayon  ) / ( 2 * d );
			var d_proj_c2:Number = d - d_proj_c1;
			
			// calcul des angles du point d'intersection, radians
			var angle_inter1:Number = Math.acos( d_proj_c1 / c1.rayon);
			var angle_inter2:Number = Math.acos( d_proj_c2 / c2.rayon );
			
			return new Lentille( c1, c2, angle, angle_inter1, angle_inter2  );
		}		
		public function toString():String
		{
			return "Circle " + name + " " + centre + " / " + rayon;
		}			
	}
}